SPDX-FileCopyrightText: 2025 Ana Beatriz Bonfim Ferreira & Larissa Silva Feital SPDX-FileCopyrightText: 2025 AlICe laboratory https://alicelab.be
SPDX-License-Identifier: GPL-3.0-or-later
Blender 4.2.3 LTS / AliceLab / AIM1 / 2024.2 John Cage - Music of Changes - Formal interpretation by procedural modeling Work: I(Change) By BONFIM Ana and FEITAL Larissa
import bpy
import random
import math1 Step: Cleaning the Scene
bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete()
bpy.ops.outliner.orphans_purge()2 Step: Defining the vertices and faces for the seven elements (notes)
elements = {
"cubeDóObj": {
"verts": [
(-1.13, -1.27, -1.06),
(-1.0, -1.0, -0.44),
(-1.0, 1.0, -1.0),
(-1.13, 1.13, -0.19),
(1.0, -1.0, -1.0),
(0.99, -1.23, -0.22),
(0.92, 1.18, -1),
(1, 1, -0.44),
],
"faces": [
(0, 1, 3, 2),
(2, 3, 7, 6),
(6, 7, 5, 4),
(4, 5, 1, 0),
(2, 6, 4, 0),
(7, 3, 1, 5),
],
},
"cubeRéObj": {
"verts": [
(-1.0, -1.0, -1.0),
(-1.11, -1.24, 0.19),
(-1.0, 1.0, -1.0),
(-1.0, 1.0, 0.02),
(1.0, -1.0, -1.0),
(1.0, -1.0, 0.02),
(1.0, 1.0, -1.0),
(1.0, 1.0, 0.02),
],
"faces": [
(0, 1, 3, 2),
(2, 3, 7, 6),
(6, 7, 5, 4),
(4, 5, 1, 0),
(2, 6, 4, 0),
(7, 3, 1, 5),
],
},
"cubeMiObj": {
"verts": [
(-1.0, -1.0, -1.0),
(-1.65, -1.45, 1.69),
(-1.0, 1.0, -1.0),
(-1.12, 0.63, 1.01),
(1.0, -1.0, -1.0),
(0.94, -0.63, 1.19),
(1.0, 1.0, -1.0),
(1.69, 0.95, 0.005),
],
"faces": [
(0, 1, 3, 2),
(2, 3, 7, 6),
(6, 7, 5, 4),
(4, 5, 1, 0),
(2, 6, 4, 0),
(7, 3, 1, 5),
],
},
"cubeFaObj": {
"verts": [
(-1.0, -1.0, -1.0),
(-0.78, -0.95, 0.24),
(-1.0, 1.0, -0.41),
(-0.78, 1.04, 0.24),
(0.87, -0.95, -0.57),
(1.21, -0.95, 0.24),
(1.0, 1.0, -1.0),
(0.89, -0.30, 1.41),
],
"faces": [
(0, 1, 3, 2),
(2, 3, 7, 6),
(6, 7, 5, 4),
(4, 5, 1, 0),
(2, 6, 4, 0),
(7, 3, 1, 5),
],
},
"cubeSolObj": {
"verts": [
(-1.54, -0.21, -1.92),
(-0.93, -0.33, 1.21),
(-0.64, 0.49, -0.40),
(-0.95, 0.57, 1.03),
(1.26, -0.37, -1.34),
(1.0, -1.0, 1.0),
(0.61, 0.16, 1.84),
(-0.61, -0.23, 3.27),
],
"faces": [
(0, 1, 3, 2),
(2, 3, 7, 6),
(6, 7, 5, 4),
(4, 5, 1, 0),
(2, 6, 4, 0),
(7, 3, 1, 5),
],
},
"cubeLaObj": {
"verts": [
(-1.0, -1.0, -1.0),
(-1.48, -1.04, 1.46),
(-1.0, 1.0, -1.0),
(-1.16, 0.09, 1.24),
(1.0, -1.0, -1.0),
(0.91, -0.48, 1.02),
(1.71, -0.23, -1.56),
(1.0, 1.0, 1.0),
],
"faces": [
(0, 1, 3, 2),
(2, 3, 7, 6),
(6, 7, 5, 4),
(4, 5, 1, 0),
(2, 6, 4, 0),
(7, 3, 1, 5),
],
},
"cubeSiObj": {
"verts": [
(-1.0, -1.0, -1.0),
(-1.65, -1.45, 1.69),
(-1.0, 1.0, -1.0),
(-1.12, 0.63, 1.01),
(1.0, -1.0, -1.0),
(0.94, -0.63, 1.19),
(1.0, 1.0, -1.0),
(1.69, 0.95, 0.005),
],
"faces": [
(0, 1, 3, 2),
(2, 3, 7, 6),
(6, 7, 5, 4),
(4, 5, 1, 0),
(2, 6, 4, 0),
(7, 3, 1, 5),
],
},
}3 Step: Randomly decide the number of elements
num_elements = random.randint(8, 64)
print(f"Generating {num_elements} elements...")4 Step: Create a 3D grid layout (cube shape) to place elements
grid_size = math.ceil(num_elements ** (1 / 3))
positions = [
(x, y, z)
for x in range(grid_size)
for y in range(grid_size)
for z in range(grid_size)
]
random.shuffle(positions)
selected_positions = positions[:num_elements]5 Step: Define a mapping for dynamics to scale factors
dynamics_to_scale = {
"ppp": 0.1,
"pp": 0.2,
"p": 0.3,
"mp": 0.4,
"mf": 0.5,
"f": 0.6,
"ff": 0.7,
"fff": 0.8,
}6 Step: Loop to place and modify objects
for pos in selected_positions:
chosen_element_name = random.choice(list(elements.keys()))
verts = elements[chosen_element_name]["verts"]
faces = elements[chosen_element_name]["faces"]Create the mesh and object
mesh = bpy.data.meshes.new(f"{chosen_element_name}_Mesh")
obj = bpy.data.objects.new(chosen_element_name, mesh)
bpy.context.collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj
mesh.from_pydata(verts, [], faces)
mesh.update()Assign a random dynamic level and apply scaling
dynamic_level = random.choice(list(dynamics_to_scale.keys()))
print(dynamic_level)
scale_factor = dynamics_to_scale[dynamic_level]
print(scale_factor)
obj.scale = (scale_factor,) * 3Table of Dynamics (For visible elements, to rotate and scale based on even and odd numbers)
obj.location = posTable of Duration (Defining the pauses)
bpy.ops.mesh.primitive_uv_sphere_add(
radius=random.choice([0.3, 0.5, 0.6]), location=pos
)
sphere = bpy.context.active_object
sphere.name = f"BooleanSphere_{pos}"
sphere.hide_viewport = True
sphere.hide_render = TrueCarving it out (Creating the sphere)
bool_modifier = obj.modifiers.new(name="Boolean", type="BOOLEAN")
bool_modifier.object = sphere
bool_modifier.operation = "DIFFERENCE"
bpy.context.view_layer.objects.active = obj
bpy.ops.object.modifier_apply(modifier="Boolean")Carving it out 2 (Making sure the sphere is removed so you can see the effect of the pauses)
bpy.data.objects.remove(sphere, do_unlink=True)Fim :)